package com.simpletour.qa.gateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.simpletour.qa.common.entity.Authority;
import com.simpletour.qa.gateway.exception.NoAuthorityException;
import com.simpletour.qa.gateway.jwt.ClaimsUtil;
import com.simpletour.qa.gateway.rpc.IAuthorityService;
import com.simpletour.qa.gateway.util.UrlRegexRuleEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * 网关权限过滤器
 *      <p>需将所有发布api录入DB并与角色绑定</p>
 *      <p>否则均无权限进行调用</p>
 * @author zhaohw
 * @date 2018-06-20 15:57
 */
@Component
@Slf4j
public class AuthorityFilter extends ZuulFilter {

    private final static String URL_EXPRESSION_PREFIX = "{";

    private final static String URL_EXPRESSION_POSTFIX = "}";

    @Autowired
    private IAuthorityService authorityService;

    @Autowired
    private FilterSkipConfig filterSkipConfig;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    private static final String REDIS_PREFIX_AUTH = "auth_";

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Object run() throws ZuulException {

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpSession httpSession = ctx.getRequest().getSession();
        HttpServletRequest request = ctx.getRequest();

        String requestUri = request.getRequestURI();
        String method = request.getMethod();

        // 如果已设置不校验登录 则必定无需校验权限 || 校验登录 不校验权限
        if(ctx.getZuulRequestHeaders().get(JwtTokenFilter.NON_CHECK_TOKEN)!=null ||
                filterSkipConfig.isSkipAuthorityURIByStartWith(requestUri,method)){
            log.info("non-check request URI : {}  method : {}",requestUri,method);
            return null;
        }
        log.info("check Auth for request URI : {}  method : {}",requestUri,method);
        Map<String,String> headers = ctx.getZuulRequestHeaders();
        String roleStr = headers.get(ClaimsUtil.CLAIM_KEY_ROLE);
        String[] roles = StringUtils.split(roleStr);

        // 缓存角色权限
        Set<Authority> authoritySet = null;
        authoritySet = (Set<Authority>) redisTemplate.opsForValue()
                .get(REDIS_PREFIX_AUTH+StringUtils.join(roles,"_"));
        if(authoritySet == null){
            authoritySet = authorityService.findAuthorityByRoleIds(roles);
            // 正则转换
            authoritySet = mapOriginalRegex(authoritySet);
            redisTemplate.opsForValue()
                    .set(REDIS_PREFIX_AUTH+StringUtils.join(roles,"_"),authoritySet);
        }

        boolean hasAuthority = authoritySet.stream()
                .filter(p->p.getType().equals(Authority.AUTHORITY_TYPE_URI))
                .filter(p->p.getMethod().equals(method))
                .map(Authority::getUri)
                .anyMatch(s -> Pattern.compile(s).matcher(requestUri).matches());
        if(!hasAuthority){
                throw new NoAuthorityException();
        }
        return null;

    }

    /**
     * 表达式转换
     * @return
     */
    private Set<Authority> mapOriginalRegex(Set<Authority> authoritySet){
        authoritySet.stream()
                .filter(a ->
                        Authority.AUTHORITY_TYPE_URI.equals(a.getType()) && a.getUri().contains(URL_EXPRESSION_PREFIX)
                )
                .forEach(a -> a.setUri(UrlRegexRuleEnum.getOriginalRegex(a.getUri())));
        return authoritySet;
    }
}
